package org.harsh.transmission.common.codec;

import cn.hutool.core.lang.UUID;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.harsh.common.Constants;
import org.harsh.common.Util;
import org.harsh.transmission.common.CustomKey;
import org.harsh.transmission.common.NodeQueue;

import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;

@Slf4j
public class EcdhClientHandler extends ChannelInboundHandlerAdapter {
    private BigInteger dB;

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.channel().attr(Constants.encryptKeyValid).set(false);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof CustomKey) {
            // CustomKey 类型进行处理，否则 提交到后面处理
            CustomKey customKey = (CustomKey) msg;
            // todo 验证 MD5 是否正确
            byte[] receivedBytes = customKey.getMsgData();

            String receivedHex = Hex.toHexString(receivedBytes);
            byte[] decodedBytes = Hex.decode(receivedHex);
            ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
            ECCurve curve = ecSpec.getCurve();
            ECPoint receivedPoint = curve.decodePoint(decodedBytes);

            ECPoint Qb;
            Qb = EC();

            byte[] QbBytes = Qb.getEncoded(true);
            long msgId = (long) Math.abs(UUID.randomUUID().hashCode());
            CustomKey clientKey = new CustomKey();
            clientKey.setMsgId(msgId);
            // @todo 此处更新版本信息
            clientKey.setVersion(customKey.getVersion());
            clientKey.setMsgType((byte)Constants.MessageType.ACKUPDATEKEY.getCode());
            clientKey.setMsgMd5(Util.byteArrMD5(QbBytes));
            clientKey.setMsgData(QbBytes);
            ctx.writeAndFlush(clientKey).addListener((ChannelFutureListener) channelFuture -> {
                if(channelFuture.isSuccess()) {
                    // 输出完，将share Key写入 Channel
                    ECPoint sharedKeyB = receivedPoint.multiply(dB).normalize();
                    BigInteger result = sharedKeyB.getAffineXCoord().toBigInteger();
                    log.info("Server PublicKey:" + receivedPoint);
                    log.info("Shared Key:" + result);
                    ctx.channel().attr(Constants.aesEncryptKey).set(result);
                    ctx.channel().attr(Constants.encryptKeyValid).set(true);
                    long validTime = System.currentTimeMillis() + 3600 * 1000l;
                    ctx.channel().attr(Constants.encryptKeyValidTime).set(validTime);
                    ctx.channel().attr(Constants.encryptKeyVersion).set(customKey.getVersion());

                    InetSocketAddress peerAddr = (InetSocketAddress)ctx.channel().remoteAddress();
                    String peerIpAddr = peerAddr.getAddress().getHostAddress();
                    int peerPort = peerAddr.getPort();
                    //Channel channel, BigInteger pubKey, BigInteger sharedKey
                    NodeQueue.insert(peerIpAddr, peerPort, ctx.channel(), dB, result);
                }
            });

        } else {
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    public ECPoint EC() throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
        keyPairGenerator.initialize(ecSpec);
        ECPoint G = ecSpec.getG();
        BigInteger n = ecSpec.getN();

        dB = new BigInteger(n.bitLength(), new SecureRandom()).mod(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
        ECPoint Qb = G.multiply(dB).normalize();
        return Qb;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
